﻿using System.Text;
using Microsoft.FluentUI.AspNetCore.Components.AssetsGenerator.Model;

namespace Microsoft.FluentUI.AspNetCore.Components.AssetsGenerator;

internal class EmojisCodeGenerator
{    
    /// <summary>
    /// Initializes a new instance of the <see cref="EmojisCodeGenerator"/> class.
    /// </summary>
    /// <param name="configuration"></param>
    public EmojisCodeGenerator(Configuration configuration)
    {
        Configuration = configuration;
        Logger = (message) => { };
    }

    /// <summary>
    /// Gets or sets the logger.
    /// </summary>
    public Action<string> Logger { get; init; }

    /// <summary>
    /// Gets the configuration.
    /// </summary>
    private Configuration Configuration { get; }

    /// <summary>
    /// Reads all SVG files in the assets folder.
    /// </summary>
    /// <returns></returns>
    public IEnumerable<Model.EmojiFileData> ReadAllAssets()
    {
        const string searchPattern = "metadata.json";
        var emojis = new List<Model.Emoji>();

        Logger.Invoke($"Reading all metadata.json files in {Configuration.AssetsFolder}.");
        var allFiles = Configuration.AssetsFolder.GetFiles(searchPattern, SearchOption.AllDirectories);

        foreach (var file in allFiles)
        {
            var newEmoji = new Model.Emoji(file);
            var key = newEmoji.Key.ToLower();

            emojis.Add(newEmoji);
        }

        return emojis.SelectMany(i => i.Files);
    }

    /// <summary>
    /// Generates all classes for the given emojis.
    /// </summary>
    /// <param name="emojis"></param>
    /// <returns></returns>
    public IEnumerable<FileInfo> GenerateClasses(IEnumerable<Model.EmojiFileData> emojis)
    {
        var generatedFiles = new List<FileInfo>();
        var allGroups = emojis.Select(i => i.Emoji.Group)
                              .Distinct()
                              .OrderBy(i => i);
        var allStyles = emojis.Select(file => file.Style)
                              .Distinct()
                              .OrderBy(i => i);
        var allSkinTones = emojis.Select(file => file.SkinTone)
                              .Distinct()
                              .OrderBy(i => i);

        // Delete previous files
        foreach (var file in Configuration.TargetFolder.GetFiles("*.cs", SearchOption.TopDirectoryOnly))
        {
            file.Delete();
        }

        // Generate all classes
        foreach (var group in allGroups)
        {
            foreach (var style in allStyles)
            {
                foreach (var skintone in allSkinTones)
                {
                    // CSharp
                    var emojisForFile = emojis.Where(i => i.Emoji.Group == group
                                                       && i.Style == style
                                                       && i.SkinTone == skintone)
                                                    .OrderBy(i => i.Emoji.Name);

                    if (emojisForFile.Any() == false)
                    {
                        continue;
                    }

                    var filename = $"{group}-{style}-{skintone}.cs";
                    var file = new FileInfo(Path.Combine(Configuration.TargetFolder.FullName, filename));

                    Logger.Invoke($"Generating {file.Name}, containing {emojisForFile.Count()} emojis.");
                    var classContent = GenerateClass(group, style, skintone, emojisForFile);

                    File.WriteAllText(file.FullName, classContent);
                    generatedFiles.Add(file);
                }
            }
        }

        return generatedFiles;
    }

    /// <summary>
    /// Generates the main class with all emojis info.
    /// </summary>
    /// <param name="icons"></param>
    /// <returns></returns>
    public FileInfo GenerateMainEmojisClass(IEnumerable<Model.EmojiFileData> emojis)
    {
        var file = new FileInfo(Path.Combine(Configuration.TargetFolder.FullName, "Emojis.cs"));

        Logger.Invoke($"Generating {file.Name}, containing {emojis.Count()} icons,");
        Logger.Invoke($"in the target folder {Configuration.TargetFolder.FullName}.");
        var classContent = GenerateMainClass(emojis);

        File.WriteAllText(file.FullName, classContent);

        return file;
    }

    /// <summary />
    private string GenerateMainClass(IEnumerable<Model.EmojiFileData> emojis)
    {
        var builder = new StringBuilder();

        builder.AppendLine("// <auto-generated>");
        builder.AppendLine("//     This code was generated by a tool.");
        builder.AppendLine("//     Changes to this file may cause incorrect behavior and will be lost if");
        builder.AppendLine("//     the code is regenerated.");
        builder.AppendLine("// </auto-generated>");
        builder.AppendLine();
        builder.AppendLine("namespace " + Configuration.Namespace + ";");
        builder.AppendLine();
        builder.AppendLine("/// <summary />");
        builder.AppendLine("[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]");
        builder.AppendLine("public static partial class Emojis");
        builder.AppendLine("{");

        // Dictionary
        builder.AppendLine("    /// <summary />");
        builder.AppendLine("    public static IEnumerable<EmojiInfo> AllEmojis");
        builder.AppendLine("    {");
        builder.AppendLine("        get");
        builder.AppendLine("        {");
        foreach (var emoji in emojis)
        {
            builder.AppendLine($"            yield return new EmojiInfo {{ Name = \"{emoji.Emoji.Name}\", Size = EmojiSize.Size{emoji.GetSizeWidth()}, Group = EmojiGroup.{emoji.Emoji.GroupKey}, Skintone = EmojiSkintone.{emoji.SkinTone}, Style = EmojiStyle.{emoji.Style}, KeyWords = \"{String.Join(',', emoji.Emoji.Keywords)}\" }};");
        }
        builder.AppendLine("        }");
        builder.AppendLine("    }");
        builder.AppendLine("}");

        return builder.ToString();
    }

    /// <summary />
    private string GenerateClass(string group, string style, string skintone, IEnumerable<Model.EmojiFileData> emojis)
    {
        var builder = new StringBuilder();

        builder.AppendLine("// <auto-generated>");
        builder.AppendLine("//     This code was generated by a tool.");
        builder.AppendLine("//     Changes to this file may cause incorrect behavior and will be lost if");
        builder.AppendLine("//     the code is regenerated.");
        builder.AppendLine("// </auto-generated>");
        builder.AppendLine();
        builder.AppendLine("#pragma warning disable 1591");
        builder.AppendLine();
        builder.AppendLine("namespace " + Configuration.Namespace + ";");
        builder.AppendLine();
        builder.AppendLine("public static partial class Emojis");
        builder.AppendLine("{");
        builder.AppendLine("    public static partial class " + group);
        builder.AppendLine("    {");
        builder.AppendLine("        public static partial class " + style);
        builder.AppendLine("        {");
        builder.AppendLine("            public static partial class " + skintone);
        builder.AppendLine("            {");

        foreach (var item in emojis)
        {
            AddProperties(builder, item);
        }

        builder.AppendLine("            }");
        builder.AppendLine("        }");
        builder.AppendLine("    }");
        builder.AppendLine("}");
        builder.AppendLine();
        builder.AppendLine("#pragma warning restore 1591");

        return builder.ToString();
    }

    private void AddProperties(StringBuilder builder, EmojiFileData file, int indentation = 16)
    {
        string indentationString = new string(' ', indentation);

        var content = file.GetContent(removeSvgRoot: true);
        var size = content.Size.Width;
        var svgContent = content.Content;
        var group = Tools.ToPascalCase(file.Emoji.Meta.Group, "_");
        var svgZipped = String.Join(", ", Tools.Zip(svgContent).Select(i => Convert.ToString(i)));
        var svgBytes = $"new byte [] {{ {svgZipped} }}";

        builder.AppendLine($"{indentationString}public class {file.Emoji.Name} : Emoji {{ public {file.Emoji.Name}() : base(\"{file.Emoji.Name}\", EmojiSize.Size{size}, EmojiGroup.{group}, EmojiSkintone.{file.SkinTone}, EmojiStyle.{file.Style}, {svgBytes}) {{ }} }}");
    }
}
